# -*- coding: utf-8 -*-
#	Copyright 2005,2006,2007,2008 Spike^ekipS <spikeekips@gmail.com>
#
#	   This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import pprint, copy, re, datetime
from django.db.models import signals
from django.dispatch import dispatcher
from django.db.models import options as options_django, fields as fields_django

import lucene
import pylucene

######################################################################
# Constants
SIGNALS = (
	"post_save",
	"post_delete",
)
MODELS_REGISTERED = dict()

FIELD_NAME_UID		= "__uid__"
FIELD_NAME_PK		= "__pk__"
FIELD_NAME_MODEL	= "__model__"

FIELDS_STR = (
	fields_django.CharField,
	fields_django.NullBooleanField,
)

FIELDS_TEXT = (
	fields_django.TextField,
	fields_django.XMLField,
)

FIELDS_KEYWORD = (
	fields_django.USStateField,
	fields_django.OrderingField,
	fields_django.NullBooleanField,
	fields_django.SlugField,
)

# int
FIELDS_INT = (
	fields_django.SmallIntegerField,
	fields_django.PositiveSmallIntegerField,
	fields_django.PositiveIntegerField,
	fields_django.IntegerField,
	fields_django.FloatField,
	fields_django.AutoField,
	fields_django.BooleanField,
	fields_django.DecimalField,
)

# meta
FIELDS_META = (
)

# date
FIELDS_DATE = (
	fields_django.TimeField,
	fields_django.DateField,
	fields_django.DateTimeField,
)
# path
FIELDS_MULTI_KEYWORD = (
	fields_django.FileField,
	fields_django.FilePathField,
	fields_django.ImageField,
	fields_django.URLField,
	fields_django.EmailField,
)

FIELDS_MULTI_KEYWORD_DELIMETER = {
	fields_django.FileField : "/",
	fields_django.FilePathField: "/",
	fields_django.ImageField: "/",
	fields_django.URLField: "/",
	fields_django.EmailField: "@",
	fields_django.CommaSeparatedIntegerField: ",",
	fields_django.IPAddressField: ".",
	fields_django.PhoneNumberField: "-",
}

FIELDS_TYPE = {
	"str": {
		"add_sort": True,
		"store": True,
		"tokenize": True,
		"flatten": True,
	},
	"text": {
		"add_sort": False,
		"store": True,
		"tokenize": True,
		"flatten": True,
	},
	"int": {
		"add_sort": True,
		"store": True,
		"tokenize": True,
		"flatten": True,
	},
	"keyword": {
		"add_sort": True,
		"store": True,
		"tokenize": False,
		"flatten": True,
	},
	"date": {
		"add_sort": True,
		"store": True,
		"tokenize": False,
		"flatten": True,
	},
	"meta": {
		"add_sort": True,
		"store": True,
		"tokenize": True,
		"flatten": True,
	},
	"multi-keyword": {
		"add_sort": True,
		"store": True,
		"tokenize": False,
		"flatten": False,
	},
}

class DocumentValue (object) :
	def get_sort_field_name (self, field_name) :
		return "sort__%s" % field_name

	def to_index (self, _type, v, delimeter=None, flatten=False, ) :
		if type(v) in (list, tuple, ) :
			return [DocumentValue.to_index(_type, i, delimeter=delimeter, flatten=flatten) for i in v]
		else :
			if _type in ("str", "int", "keyword", "meta", "text", ) :
				v = [str(v), ]
			elif _type == "date" :
				if isinstance(v, datetime.datetime) :
					v = [v.strftime("%Y%m%d%H%M%S"), ]
				else :
					v = [str(v), ]
			elif _type == "multi-keyword" :
				if not flatten and delimeter :
					if not v :
						v = ""
					else :
						v0 = v.split(delimeter)
						v0.extend([v, ])
						v = v0
				else :
					v = [str(v), ]

			return flatten and "".join(v) or v

	def from_index (self, _type, v, kind=None) :
		if _type in ("str", "text", ) :
			return str(v)
		elif _type in ("int", ) :
			return int(v)
		elif _type == "date" :
			d = datetime.datetime.strptime(v, "%Y%m%d%H%M%S")

			if kind :
				_y, _m, _d = d.year, d.month, d.day
				if kind == "year" :
					_m, _d, = 1, 1
				elif kind == "month" :
					_d = 1

				return datetime.date(_y, _m, _d)
			else :
				return d
		elif _type in ("multi-keyword", "keyword", ) :
			return v

	from_index = classmethod(from_index)
	to_index = classmethod(to_index)
	get_sort_field_name = classmethod(get_sort_field_name)

class Document (object) :

	def __init__ (self, obj, query=None) :
		(self.hit, self.doc, ) = obj

		self.model_info = MODELS_REGISTERED.get(self.doc.get(FIELD_NAME_MODEL), None)

	def __unicode__ (self) :
		return "<Document:%s>" % self.doc.get(FIELD_NAME_UID)

	__str__ = __unicode__
	__repr__ = __unicode__

	def get_value (self, k) :
		if self.model_info["fields"].has_key(k) :
			return (
				self.model_info["fields"].get(k).get("type"),
				self.doc.getValues(k),
			)

		elif k == "pk" :
			return (
				"int", 
				self.doc.getValues(FIELD_NAME_PK),
			)

		raise ValueError

	def __getitem__ (self, k) :
		(_type, v, ) = self.get_value(k)
		return DocumentValue.from_index(_type, v[-1])

	def get (self, k, default=None, kind=None) :
		try :
			(_type, v, ) = self.get_value(k)
			return DocumentValue.from_index(_type, v[-1], kind=kind)
		except ValueError :
			return default

	def getlist (self, k) :
		try :
			(_type, v, ) = self.get_value(k)
			return [DocumentValue.from_index(_type, i) for i in v]
		except ValueError :
			return default



class Model (object) :

	def get_info (self, model) :
		return MODELS_REGISTERED.get(Model.get_name(model), None)

	def get_uid (self, model, pk) :
		return "%s/%s" % (self.get_name(model), pk, )

	def get_field_name (self, field) :
		return field.name

	def get_name (self, model) :
		return "%s.%s" % (model._meta.app_label, model._meta.object_name)

	def get_meta (self, model) :
		return {
			"verbose_name": model._meta.verbose_name,
			"pk_name": model._meta.pk.name,
			"ordering": model._meta.ordering,
		}

	def get_field_type (self, field) :
		if field.__class__ in FIELDS_STR :
			return "str"
		elif field.__class__ in FIELDS_TEXT :
			return "text"
		elif field.__class__ in FIELDS_KEYWORD :
			return "keyword"
		elif field.__class__ in FIELDS_INT :
			return "int"
		elif field.__class__ in FIELDS_DATE :
			return "date"
		elif field.__class__ in FIELDS_META :
			return "meta"
		elif field.__class__ in FIELDS_MULTI_KEYWORD :
			return "multi-keyword"
		else :
			return None

	def analyze_field (self, field, **kwargs) :
		analyzer = kwargs.get("analyzer", None)
		if not analyzer :
			analyzer = lucene.CJKAnalyzer()

		_delimeter = None
		__type = self.get_field_type(field)
		if __type is None :
			return None

		if __type == "multi-keyword":
			_delimeter = FIELDS_MULTI_KEYWORD_DELIMETER.get(field.__class__, None)

		attr = copy.deepcopy(FIELDS_TYPE.get(__type, None))
		if attr is None :
			attr = {
				"add_sort": True,
				"store": True,
				"tokenize": True,
			}

		__field_name = self.get_field_name(field)
		attr.update(
			{
				"delimeter": _delimeter,
				"name": __field_name,
				"attrname": __field_name,
				"type": __type,
				"analyzer": analyzer,
			}
		)
		attr.update(kwargs)

		return attr

	def analyze (self, model, **kwargs) :
		# get fields
		__fields = dict()
		for f in model._meta.fields :
			i = self.analyze_field(
				f,
				**kwargs.get("fields", dict()).get(
					self.get_field_name(f), dict()
				)
			)
			if not i :
				continue

			__fields[f.name] = i

		__additional_fields = {
				FIELD_NAME_UID: {
					"add_sort": False,
					"store": True,
					"tokenize": False,
					"delimeter": None,
					"name": FIELD_NAME_UID,
					"attrname": FIELD_NAME_UID,
					"type": "keyword",
					"analyzer": None,
				},
				FIELD_NAME_PK: {
					"add_sort": False,
					"store": True,
					"tokenize": False,
					"delimeter": None,
					"name": FIELD_NAME_PK,
					"attrname": FIELD_NAME_PK,
					"type": "int",
					"analyzer": None,
				},
				FIELD_NAME_MODEL: {
					"add_sort": False,
					"store": True,
					"tokenize": False,
					"delimeter": None,
					"name": FIELD_NAME_MODEL,
					"attrname": FIELD_NAME_MODEL,
					"type": "keyword",
					"analyzer": None,
				},
		}

		for f, v in __fields.items() :
			if v.get("add_sort") :
				__field_name = DocumentValue.get_sort_field_name(v.get("attrname"))
				__additional_fields[__field_name] = copy.deepcopy(v)
				__additional_fields[__field_name].update(
					{
						"name": __field_name,
						"store": False,
						"tokenize": False,
						"flatten": True,
					}
				)

		__fields.update(__additional_fields)

		return {
			"fields": __fields,
			"meta": self.get_meta(model),
			"name": self.get_name(model),
		}

	get_info		= classmethod(get_info)
	get_uid			= classmethod(get_uid)
	get_name		= classmethod(get_name)
	get_meta		= classmethod(get_meta)
	get_field_name	= classmethod(get_field_name)
	analyze			= classmethod(analyze)
	analyze_field	= classmethod(analyze_field)
	get_field_type	= classmethod(get_field_type)

class Signals (object) :

	def connect (self, model, signal_name) :
		dispatcher.connect(
			getattr(self, signal_name),
			sender=model,
			signal=getattr(signals, signal_name),
			weak=False,
		)

	connect = 	classmethod(connect)

	def pre_save (self, instance=None, sender=None, created=False, **kwargs) :
		pass

	def post_save (self, instance=None, sender=None, created=False, **kwargs) :
		if created :
			pylucene.index_object(instance)
		else :
			pylucene.index_update_object(instance)

	def pre_delete (self, instance=None, sender=None, **kwargs) :
		pass

	def post_delete (self, instance=None, sender=None, **kwargs) :
		pylucene.unindex_object(instance)

	pre_save	= classmethod(pre_save)
	post_save	= classmethod(post_save)
	pre_delete	= classmethod(pre_delete)
	post_delete	= classmethod(post_delete)

def register (model, **kwargs) :
	from manager import Manager

	# attach django model manager
	model.objects_search = Manager()
	model.objects_search.contribute_to_class(model, "objects_search")

	# analyze model
	MODELS_REGISTERED.update(
		{
			Model.get_name(model) : Model.analyze(model, **kwargs)
		}
	)


"""
Description
-----------


ChangeLog
---------


Usage
-----


"""

__author__ =  "Spike^ekipS <spikeekips@gmail.com>"




